﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CardTypes = Wormhole.Engine.CardLibrary.CardTypes;
using CardProperties = Wormhole.Engine.CardLibrary.CardProperties;
using Communication = Wormhole.Engine.Communication;

namespace Wormhole.Engine
{
	/// <author>Rodney McKay</author>
	/// <date>10 April 2008</date>
	/// <summary>
	/// This class represents a game in the engine.
	/// </summary>
	public class Game
	{
		#region Delegates
		/// <summary>
		/// The delegate for the Mission Played event.
		/// </summary>
		/// <param name="mission">The mission that was played.</param>
		public delegate void MissionPlayedDelegate(Wormhole.Engine.CardLibrary.CardTypes.Mission mission);

		/// <summary>
		/// The delegate for the event that is raised when the game is started.
		/// </summary>
		public delegate void GameStartedDelegate();

		public delegate void GameActionDelegate();
		#endregion

		#region Events
		/// <summary>
		/// This event fires when a mission is played.
		/// </summary>
		public event MissionPlayedDelegate MissionPlayed;

		/// <summary>
		/// This event is raised when and open game is received.
		/// </summary>
		public event Communication.OpenGameReceivedDelegate OpenGameReceived;

		/// <summary>
		/// The event that is raised when a game is no longer available.
		/// </summary>
		public event Communication.GameClosedDelegate GameClosed;

		/// <summary>
		/// This event is raised when a response to a game join request is received.
		/// </summary>
		public event Communication.JoinResponseDelegate JoinResponse;

		/// <summary>
		/// This event is raised when the game is started.
		/// </summary>
		public event GameStartedDelegate GameStarted;

		/// <summary>
		/// This event is raised when the opponent should be asked to take a mulligan or not.
		/// </summary>
		public event GameActionDelegate AskOpponentMulligan;

		#endregion

		#region Declarations
		/// <summary>
		/// The communication client for two player games.
		/// </summary>
		protected Communication.IClient communication;

		/// <summary>
		/// The object that will dispatch events over the communications channel.
		/// </summary>
		protected Communication.Dispatcher dispatcher;

		/// <summary>
		/// The object that will receive events from the other player over the communications channel.
		/// </summary>
		protected Communication.Receiver receiver;
		#endregion

		#region Properties

		#region Player 1
		/// <summary>
		/// The player 1 object.
		/// </summary>
		public Player.Player Player1 { get; set; }
		#endregion

		#region Player 2
		/// <summary>
		/// The player 2 object.
		/// </summary>
		public Player.Player Player2 { get; set; }
		#endregion

		#region Mission
		/// <summary>
		/// The mission that is being played.
		/// </summary>
		public Mission.Mission Mission { get; set; }
		#endregion

		#region Game Turn
		/// <summary>
		/// The game turn.
		/// </summary>
		public Turn.GameTurn GameTurn { get; set; }
		#endregion

		#region Joined
		private bool _joined;
		/// <summary>
		/// Was the game joined by another player.
		/// </summary>
		public bool Joined
		{
			get { return this._joined; }
		}
		#endregion

		#region Game Creator
		/// <summary>
		/// Was the game created by this player.
		/// </summary>
		public bool GameCreator { get; set; }
		#endregion

		#region Random Seed
		/// <summary>
		/// The seed to use for randomization.
		/// </summary>
		public int RandomSeed { get; set; }
		#endregion

		#endregion

		#region Constructor
		/// <summary>
		/// Initialize a default instance of the class.
		/// </summary>
		public Game()
		{
			this.RandomSeed = 1;
			this.GameCreator = false;
			
			this.Mission = new Mission.Mission(this);
			this.Player1 = new Player.Player(this);
			this.Player2 = new Player.Player(this);

			this.GameTurn = new Turn.GameTurn(this);

			this.SetEngineEvents();
		}
		#endregion

		#region Connect
		/// <summary>
		/// Connect to the communications server for a two player game.
		/// </summary>
		/// <param name="server"></param>
		/// <param name="port"></param>
		/// <param name="playerName"></param>
		public void Connect(string server, int port, string playerName)
		{
			// create the communications client
			this.Player1.Name = playerName;
			this.communication = new Communication.IrcClient(server, port, "#wormhole_lobby", playerName);

			// request a list of open games
			this.RequestOpenGameList();

			// wire up the events
			this.SetupCommunicationsEvents();
		}
		#endregion

		#region Request Open Game List
		/// <summary>
		/// Request a list of open games.
		/// </summary>
		public void RequestOpenGameList()
		{
			this.communication.RequestOpenGameList();
		}
		#endregion

		#region Setup Communications Events
		/// <summary>
		/// Wire up the events for the communications channel.
		/// </summary>
		private void SetupCommunicationsEvents()
		{
			this.communication.OpenGameReceived += new Wormhole.Engine.Communication.OpenGameReceivedDelegate(communication_OpenGameReceived);
			this.communication.GameClosed += new Wormhole.Engine.Communication.GameClosedDelegate(communication_GameClosed);
			this.communication.JoinResponse += new Wormhole.Engine.Communication.JoinResponseDelegate(communication_JoinResponse);
			this.communication.JoinRequest += new Wormhole.Engine.Communication.JoinRequestDelegate(communication_JoinRequest);
			this.communication.RandomSeedReceived += new Wormhole.Engine.Communication.RandomSeedReceivedDelegate(communication_RandomSeedReceived);
			this.communication.DeckReceived += new Wormhole.Engine.Communication.DeckReceivedDelegate(communication_DeckReceived);
		}
		#endregion

		#region Disconnect
		/// <summary>
		/// Disconnect from the communications server.
		/// </summary>
		public void Disconnect()
		{
			if (this.communication != null)
				this.communication.Disconnect();
		}
		#endregion

		#region Create Game
		/// <summary>
		/// Create a new game.
		/// </summary>
		/// <param name="description">The description for the new game.</param>
		public void CreateGame(string description)
		{
			this.GameCreator = true;
			this.communication.CreateGame(description);
		}
		#endregion

		#region Close Game
		/// <summary>
		/// Close a game.
		/// </summary>
		public void CloseGame()
		{
			this.GameCreator = false;
			this.communication.CloseGame(true);
		}
		#endregion

		#region Join Game
		/// <summary>
		/// Dispatch a request to join a game.
		/// </summary>
		/// <param name="game">The game the request is for.</param>
		public void JoinGame(Communication.Game game)
		{
			this.communication.JoinGame(game.ID);
		}
		#endregion

		#region On Open Game Received
		/// <summary>
		/// Raise the open game received event.
		/// </summary>
		/// <param name="game">The game that was received.</param>
		protected void OnOpenGameReceived(Communication.Game game)
		{
			// raise the event
			if (this.OpenGameReceived != null)
				this.OpenGameReceived(game);
		}
		#endregion

		#region On Game Closed
		/// <summary>
		/// Raise the game closed event.
		/// </summary>
		/// <param name="game">The game that was closed.</param>
		protected void OnGameClosed(Communication.Game game)
		{
			if (this.GameClosed != null)
				this.GameClosed(game);
		}
		#endregion

		#region On Join Response
		/// <summary>
		/// Fire the join response message.
		/// </summary>
		/// <param name="game">The game the message is for.</param>
		/// <param name="accepted">True when the request is accepted.</param>
		/// <param name="message">The response message.</param>
		protected void OnJoinResponse(Communication.Game game, bool accepted, string message)
		{
			// indicate that the game was joined
			if (accepted)
				this._joined = true;

			// fire the event
			if (this.JoinResponse != null)
				this.JoinResponse(game, accepted, message);

			// start an accepted game
			if (accepted)
				this.HandleAcceptedGame(game);
		}
		#endregion

		#region Handle Accepted Game
		/// <summary>
		/// Handle a game that was accepted by the opponent.
		/// </summary>
		/// <param name="game"></param>
		private void HandleAcceptedGame(Wormhole.Engine.Communication.Game game)
		{
			this._joined = true;

			// set the player names
			this.Player1.Name = game.PlayerName;
			this.Player2.Name = game.OpponentName;

			// sync the random seeds
			this.Player1.RandomSeed = this.RandomSeed;
			this.communication.SendRandomSeed(this.RandomSeed);
		}
		#endregion

		#region Handle Random Seed Received
		/// <summary>
		/// Handle the reception of the opponent random seed.
		/// </summary>
		private void HandleRandomSeedReceived(int seed)
		{
			// set the random seed of the opponent
			this.Player2.RandomSeed = seed;

			// sync the deck with the opponent
			this.SendDeck();
		}
		#endregion

		#region Handle Deck Received
		/// <summary>
		/// Handle the deck that was received from the opponent.
		/// </summary>
		/// <param name="deck">The communications deck that was received.</param>
		private void HandleDeckReceived(Communication.Deck deck)
		{
			// set the IDs of the cards
			for (int i = 0; i < deck.Count; i++)
			{
				// get the cards
				Communication.Card comCard = deck[i];
				CardLibrary.BaseCardTypes.Card card = this.Player2.Deck[i];

				// set the id of the card
				card.ID = comCard.ID;
			}

			// choose the player to start
			this.ChooseStartPlayer();
		}
		#endregion

		#region Choose Start Player
		/// <summary>
		/// Chose the player to start as the hero.
		/// </summary>
		public void ChooseStartPlayer()
		{
			if (this.GameCreator)
			{
				this.GameTurn.HeroPlayer = this.Player1;
				this.GameTurn.VillianPlayer = this.Player2;
			}
			else
			{
				this.GameTurn.HeroPlayer = this.Player2;
				this.GameTurn.VillianPlayer = this.Player1;
			}

			// raise the game started event (main.xaml then calls Start())
			this.OnGameStarted();
		}
		#endregion

		#region Send Deck
		/// <summary>
		/// Send the deck of cards to the opponent.
		/// </summary>
		private void SendDeck()
		{
			// initialize the card id
			for (int i = 0; i < this.Player1.Deck.Count; i++)
			{
				this.Player1.Deck[i].ID = i;
			}

			// create the sync deck
			Communication.Deck deck = new Communication.Deck();

			foreach (CardLibrary.BaseCardTypes.Card card in this.Player1.Deck)
			{
				// create the sync card
				Communication.Card comCard = new Wormhole.Engine.Communication.Card();
				comCard.ID = card.ID;

				// add the card to the deck to send
				deck.Add(comCard);
			}

			// send the deck
			this.communication.SendDeck(deck);
		}
		#endregion

		#region Start
		/// <summary>
		/// Start a new game.
		/// </summary>
		public void Start()
		{
			// setup the player all card collection
			this.Player1.InitializeAllCards();
			this.Player2.InitializeAllCards();

			// remove the team cards
			this.Player1.Deck.RemoveTeamCards();
			this.Player2.Deck.RemoveTeamCards();

			// remove the mission cards
			this.Player1.Deck.RemoveMissionCards();
			this.Player2.Deck.RemoveMissionCards();

			this.Player1.Missions.Shuffle();
			this.Player2.Missions.Shuffle();

			// shuffle the deck
			this.Player1.Deck.Shuffle();
			this.Player2.Deck.Shuffle();

			// get the victory total
			this.Player1.Score.VictoryTotal = this.Player2.Team.VictoryTotal;
			this.Player2.Score.VictoryTotal = this.Player1.Team.VictoryTotal;

			// fill the players hands.
			this.Player1.FillHand();
			this.Player2.FillHand();

			// create the communication channel listerners
			this.dispatcher = new Wormhole.Engine.Communication.Dispatcher(this, this.communication);
			this.receiver = new Wormhole.Engine.Communication.Receiver(this, this.communication);

			// set the player priorities
			this.Player1.HasPriority = this.GameCreator;
			this.Player2.HasPriority = !this.Player1.HasPriority;
						
			// ask the player to keep the deck
			if (this.GameTurn.HeroPlayer == this.Player1)
				this.AskMulligan();
			else
				this.Player1.UserInput.DisplayMessage("Please wait while the opponent decides on keeping his hand.");

		}
		#endregion

		#region Ask Mulligan
		/// <summary>
		/// Ask the player if he wants to take a mulligan.
		/// </summary>
		public void AskMulligan()
		{
			this.Player1.UserInput.KeepHandInput(new Input.Request.CallbackDelegate(this.ProcessKeepHand));
		}
		#endregion

		#region Process Keep Hand
		/// <summary>
		/// Process the user input from the keep hand input.
		/// </summary>
		/// <param name="response">The response details.</param>
		public void ProcessKeepHand(Input.Response response)
		{
			// handle the response
			if (response.ResponseType == Input.ResponseType.No)
				this.Player1.TakeMulligan();
			else
				this.Player1.KeepHand();

			// if this player created the game, give the other player a turn to choose to take a mulligan
			if (this.GameCreator)
			{
				// GameTurn.StartTurn is called from Channel_TookMulligan/KeepHand in the communications receiver
				this.Player1.UserInput.DisplayMessage("Please wait while the opponent decides on keeping his hand.");
				this.OnAskOpponentMulligan();
			}
			else
			{
				this.GameTurn.StartTurn();
			}
		}
		#endregion

		#region Ask Opponent Mulligan
		/// <summary>
		/// Give the opponent the opportunity to take a mulligan
		/// </summary>
		private void OnAskOpponentMulligan()
		{
			if (this.AskOpponentMulligan != null)
				this.AskOpponentMulligan();
		}
		#endregion

		#region Set Engine Events
		/// <summary>
		/// Wire up the engine events that the object will listen for.
		/// </summary>
		protected void SetEngineEvents()
		{
			this.Mission.SetEngineEvents();
			this.Player1.Missions.MissionPlayed += new Wormhole.Engine.Decks.MissionCards.MissionPlayedDelegate(Missions_MissionPlayed);
			this.Player2.Missions.MissionPlayed += new Wormhole.Engine.Decks.MissionCards.MissionPlayedDelegate(Missions_MissionPlayed);

			this.Player1.GotPriority += new Wormhole.Engine.Player.Player.PlayerActionDelegate(Player_GotPriority);
			this.Player2.GotPriority += new Wormhole.Engine.Player.Player.PlayerActionDelegate(Player_GotPriority);
		}
		#endregion

		#region Event Handlers

		#region Mission Played
		/// <summary>
		/// The event handler for when a mission is played.
		/// </summary>
		/// <param name="mission">The mission card that was played.</param>
		private void Missions_MissionPlayed(Wormhole.Engine.CardLibrary.CardTypes.Mission mission)
		{
			this.OnMissionPlayed(mission);
		}
		#endregion

		#region Open Game Received
		/// <summary>
		/// Handle an open game that was received via the communications channel.
		/// </summary>
		/// <param name="game"></param>
		private void communication_OpenGameReceived(Wormhole.Engine.Communication.Game game)
		{
			this.OnOpenGameReceived(game);
		}
		#endregion

		#region Game Closed
		/// <summary>
		/// Handle the game closed event.
		/// </summary>
		/// <param name="game">The game that was closed.</param>
		private void communication_GameClosed(Wormhole.Engine.Communication.Game game)
		{
			this.OnGameClosed(game);
		}
		#endregion

		#region Join Response
		/// <summary>
		/// Handle the game join response message.
		/// </summary>
		/// <param name="game"></param>
		/// <param name="accepted"></param>
		/// <param name="message"></param>
		private void communication_JoinResponse(Wormhole.Engine.Communication.Game game, bool accepted, string message)
		{
			this.OnJoinResponse(game, accepted, message);
		}
		#endregion

		#region Join Request
		/// <summary>
		/// Autonmatically accept all game join requests.
		/// </summary>
		/// <param name="game"></param>
		/// <param name="playerName"></param>
		private void communication_JoinRequest(Wormhole.Engine.Communication.Game game, string playerName)
		{
			this.communication.AcceptJoin(game.ID, playerName, string.Empty);
			this.OnJoinResponse(game, true, string.Empty);
		}
		#endregion

		#region Random Seed Received
		/// <summary>
		/// Receive the random seed of the opponent.
		/// </summary>
		/// <param name="seed"></param>
		public void communication_RandomSeedReceived(int seed)
		{
			this.HandleRandomSeedReceived(seed);
		}
		#endregion

		#region Deck Received
		/// <summary>
		/// The event that is raised when the deck of the opponent is received.
		/// </summary>
		/// <param name="deck"></param>
		private void communication_DeckReceived(Wormhole.Engine.Communication.Deck deck)
		{
			this.HandleDeckReceived(deck);
		}
		#endregion

		#region Got Priority
		/// <summary>
		/// Handle the got priority player event.
		/// </summary>
		/// <param name="player"></param>
		private void Player_GotPriority(Wormhole.Engine.Player.Player player)
		{
			this.HandlePriority(player);
		}
		#endregion

		#endregion

		#region Handle Priority
		/// <summary>
		/// Handle priority shifts.
		/// </summary>
		/// <param name="player">The player who got priority.</param>
		private void HandlePriority(Player.Player player)
		{
			// initialize the priorities
			this.Player1.HasPriority = false;
			this.Player2.HasPriority = false;

			// give the player prority who got it
			player.HasPriority = true;

			// if the other player got the priority wait for him to give it back
			if (player != this.Player1)
				this.Player1.UserInput.DisplayMessage("Please wait for the opponent to complete any actions.");
		}
		#endregion

		#region On Mission Played
		/// <summary>
		/// Raise the mission played event.
		/// </summary>
		protected void OnMissionPlayed(Wormhole.Engine.CardLibrary.CardTypes.Mission mission)
		{
			if (this.MissionPlayed != null)
				this.MissionPlayed(mission);
		}
		#endregion

		#region On Game Started
		/// <summary>
		/// Fire the game started event.
		/// </summary>
		protected void OnGameStarted()
		{
			if (this.GameStarted != null)
				this.GameStarted();
		}
		#endregion
	}
}
